#pragma once

#include <QtCore/QUuid>
#include <QtWidgets/QGraphicsScene>

#include <unordered_map>
#include <tuple>
#include <functional>

#include "Export.hpp"
#include "QtDataModelRegistry.hpp"

namespace Qt
{

	class QtNodeDataModel;
	class FlowItemInterface;
	class QtNode;
	class QtConnection;
	class QtNodeGraphicsObject;
	class QtConnectionGraphicsObject;
	class NodeStyle;

	/// Scene holds connections and nodes.
	class NODE_EDITOR_PUBLIC QtFlowScene
		: public QGraphicsScene
	{
		Q_OBJECT
	public:

		QtFlowScene(std::shared_ptr<QtDataModelRegistry> registry,
			QObject * parent = Q_NULLPTR);

		QtFlowScene(QObject * parent = Q_NULLPTR);

		~QtFlowScene();

	public:

		std::shared_ptr<QtConnection>
			createConnection(PortType connectedPort,
				QtNode& node,
				PortIndex portIndex);

		std::shared_ptr<QtConnection>
			createConnection(QtNode& nodeIn,
				PortIndex portIndexIn,
				QtNode& nodeOut,
				PortIndex portIndexOut,
				TypeConverter const & converter = TypeConverter{});

		std::shared_ptr<QtConnection> restoreConnection(QJsonObject const &connectionJson);

		void deleteConnection(QtConnection& connection);

		QtNode&createNode(std::unique_ptr<QtNodeDataModel> && dataModel);

		QtNode&restoreNode(QJsonObject const& nodeJson);

		QtDataModelRegistry&registry() const;

		void setRegistry(std::shared_ptr<QtDataModelRegistry> registry);

		void iterateOverNodes(std::function<void(QtNode*)> const & visitor);

		void iterateOverNodeData(std::function<void(QtNodeDataModel*)> const & visitor);

		void iterateOverNodeDataDependentOrder(std::function<void(QtNodeDataModel*)> const & visitor);

		QPointF getNodePosition(QtNode const& node) const;

		void setNodePosition(QtNode& node, QPointF const& pos) const;

		QSizeF getNodeSize(QtNode const& node) const;

		void removeNode(QtNode& node);

		void clearNode(QtNode& node);

	public:

		std::unordered_map<QUuid, std::unique_ptr<QtNode> > const & nodes() const;

		std::unordered_map<QUuid, std::shared_ptr<QtConnection> > const & connections() const;

		std::vector<QtNode*> allNodes() const;

		std::vector<QtNode*> selectedNodes() const;

	public:

		void clearScene();

		void save() const;

		void load();

		QByteArray saveToMemory() const;

		void loadFromMemory(const QByteArray& data);

	Q_SIGNALS:

		/**
		 * @brief QtNode has been created but not on the scene yet.
		 * @see nodePlaced()
		 */
		void nodeCreated(QtNode &n);

		/**
		 * @brief QtNode has been added to the scene.
		 * @details Connect to this signal if need a correct position of node.
		 * @see nodeCreated()
		 */
		void nodePlaced(QtNode &n);

		void nodeDeleted(QtNode &n);

		void connectionCreated(QtConnection const &c);
		void connectionDeleted(QtConnection const &c);

		void nodeMoved(QtNode& n, const QPointF& newLocation);

		void nodeDoubleClicked(QtNode& n);

		void nodeSelected(QtNode& n);

		void nodeHotKey0Checked(QtNode& n, bool checked);

		void nodeHotKey1Checked(QtNode& n, bool checked);

		void nodeHotKey2Checked(QtNode& n, bool checked);

		void connectionHovered(QtConnection& c, QPoint screenPos);

		void nodeHovered(QtNode& n, QPoint screenPos);

		void connectionHoverLeft(QtConnection& c);

		void nodeHoverLeft(QtNode& n);

		void nodeContextMenu(QtNode& n, const QPointF& pos);

		void outPortConextMenu(QtNode& n, const PortIndex index, const QPointF& pos);

		void menuHelp(QtNode& n);

		void menuOpen(QtNode& n);

	private:

		using SharedConnection = std::shared_ptr<QtConnection>;
		using UniqueNode = std::unique_ptr<QtNode>;

		// DO NOT reorder this member to go after the others.
		// This should outlive all the connections and nodes of
		// the graph, so that nodes can potentially have pointers into it,
		// which is why it comes first in the class.
		std::shared_ptr<QtDataModelRegistry> _registry;

		std::unordered_map<QUuid, SharedConnection> _connections;
		std::unordered_map<QUuid, UniqueNode>       _nodes;

	private Q_SLOTS:

		void setupConnectionSignals(QtConnection const& c);

		void sendConnectionCreatedToNodes(QtConnection const& c);
		void sendConnectionDeletedToNodes(QtConnection const& c);

	};

	QtNode*
		locateNodeAt(QPointF scenePoint, QtFlowScene &scene,
			QTransform const & viewTransform);
}
